﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using StringMath;

namespace PiCalcComm
{
    public class Server : Computer
    {
        public static int DigitsToCalcualte = 10;
        public static int HexDigitsCalcuated = 0;
        public static int StartInt = 0;
        public static int UsersConnected = 0;
        public static Thread AddThread;
        public static MathString Pi = new MathString("3", "0");
        public static List<MathString> NumbersToAdd = new List<MathString> { };
        public static object Locker = new object();

        public bool disconnected = false;

        public Server(Socket Connecton)
            : base(Connecton)
        {
            UsersConnected ++;
            if (AddThread == null)
            {
                AddThread = new Thread(new ThreadStart(AsyncAddNumbers));
                AddThread.Start();
            }
            Thread serverThead = new Thread(new ThreadStart(Thread));
            serverThead.Start();
        }
        public void Thread()
        {
            while (true)
            {
                lock (Locker)
                {
                    Send(StartInt);
                    StartInt += DigitsToCalcualte;
                    Send(StartInt);
                }
                MathString RecievedNumber = RecieveMathString();
                lock (NumbersToAdd)
                    NumbersToAdd.Add(RecievedNumber);
                lock (Locker)
                    HexDigitsCalcuated += DigitsToCalcualte;
                UpdateScreen();
            }
        }
        public static void AsyncAddNumbers()
        {
            while (true)
            {
                if (NumbersToAdd.Count != 0)
                {
                    lock (NumbersToAdd)
                    {
                        Pi += NumbersToAdd[0];
                        NumbersToAdd.RemoveAt(0);
                    }
                }
                else
                {
                    System.Threading.Thread.Sleep(10000);
                }
            }
        }
        private static void UpdateScreen()
        {
            Console.Clear();
            Console.WriteLine("{0} Clients Connected", UsersConnected);
            Console.WriteLine("{0} Digits Calculated, {1} Remaining", HexDigitsCalcuated, 1000000 - HexDigitsCalcuated);
        }
    }
    public class Client : Computer
    {
        public static int INFINITY = 1000000;
        public int StartInt = 0;
        public int EndInt = 0;
        public Client(Socket Connection)
            : base(Connection)
        {
            Thread();
        }
        public void Thread()
        {
            while (true)
            {
                MathString Output = new MathString("0", "0");
                StartInt = RecieveInt();
                EndInt = RecieveInt();
                for (int i = StartInt; i < EndInt; )
                {
                    int[] HexDigits = GetDigit(i, 10);
                    for (int j = 0; j < 10; j++)
                    {
                        int ThisDigit = HexDigits[j];
                        MathString HexValue = MathString.Parse(ThisDigit);
                        MathString Exponent = new MathString("16", "0");
                        Exponent = Exponent ^ (i + 1);
                        HexValue = HexValue / Exponent;
                        Output += HexValue;
                        i++;
                        UpdateScreen(Output, StartInt, i, EndInt);
                    }

                }
                Send(Output);
            }
        }
        private static void UpdateScreen(MathString Output, int start, int current, int end)
        {
            Console.Clear();
            Console.WriteLine("{0} Digits Calculated, {1} Remaining", current - start, end - current);
            Console.WriteLine(Output);
        }
        public int GetDigit(int n)
        {
            /*
             * pi = 	4 sum(0 , inf, (1) / ((16^k)(8k+1))) 
             * - 2sum(0 , inf, (1) / ((16^k)(8k+4)))
             * - sum(0 , inf, (1) / ((16^k)(8k+5)))
             * - sum(0 , inf, (1) / ((16^k)(8k+6)))
             * sum1 = sum(0,n,(16^(n-k) % (8k + 1) / (8K + 1)) + sum(n + 1, inf, (16^(n-k))/(8k + 1))
             * sum2 = sum(0,n,(16^(n-k) % (8k + 4) / (8K + 4)) + sum(n + 1, inf, (16^(n-k))/(8k + 4))
             * sum3 = sum(0,n,(16^(n-k) % (8k + 5) / (8K + 5)) + sum(n + 1, inf, (16^(n-k))/(8k + 5))
             * sum4 = sum(0,n,(16^(n-k) % (8k + 6) / (8K + 6)) + sum(n + 1, inf, (16^(n-k))/(8k + 6))
             * */

            double sum1 = 0;
            double sum2 = 0;
            double sum3 = 0;
            double sum4 = 0;

            for (int k = 0; k <= n; k++)
            {
                double this1 = (double)(MemoryEfficientModulus(16, (n - k), (8 * k + 1))) / (double)(8 * k + 1);
                double this2 = (double)(MemoryEfficientModulus(16, (n - k), (8 * k + 4))) / (double)(8 * k + 4);
                double this3 = (double)(MemoryEfficientModulus(16, (n - k), (8 * k + 5))) / (double)(8 * k + 5);
                double this4 = (double)(MemoryEfficientModulus(16, (n - k), (8 * k + 6))) / (double)(8 * k + 6);
                sum1 += this1;
                sum2 += this2;
                sum3 += this3;
                sum4 += this4;
            }
            for (int k = n + 1; k < INFINITY; k++)
            {
                double this1 = (Math.Pow(16, n - k) / (8 * k + 1));
                double this2 = (Math.Pow(16, n - k) / (8 * k + 4));
                double this3 = (Math.Pow(16, n - k) / (8 * k + 5));
                double this4 = (Math.Pow(16, n - k) / (8 * k + 6));
                sum1 += this1;
                sum2 += this2;
                sum3 += this3;
                sum4 += this4;
            }
            double total = 4 * sum1 - 2 * sum2 - sum3 - sum4;
            double floor = Math.Floor(total);
            total -= floor;
            total = total * 16;

            int output = (int)Math.Floor(total);
            if (output < 0)
                output += 15;
            return output;
        }
        public int[] GetDigit(int start, int number)
        {
            /*
 * pi = 	4 sum(0 , inf, (1) / ((16^k)(8k+1))) 
 * - 2sum(0 , inf, (1) / ((16^k)(8k+4)))
 * - sum(0 , inf, (1) / ((16^k)(8k+5)))
 * - sum(0 , inf, (1) / ((16^k)(8k+6)))
 * sum1 = sum(0,n,(16^(n-k) % (8k + 1) / (8K + 1)) + sum(n + 1, inf, (16^(n-k))/(8k + 1))
 * sum2 = sum(0,n,(16^(n-k) % (8k + 4) / (8K + 4)) + sum(n + 1, inf, (16^(n-k))/(8k + 4))
 * sum3 = sum(0,n,(16^(n-k) % (8k + 5) / (8K + 5)) + sum(n + 1, inf, (16^(n-k))/(8k + 5))
 * sum4 = sum(0,n,(16^(n-k) % (8k + 6) / (8K + 6)) + sum(n + 1, inf, (16^(n-k))/(8k + 6))
 * */

            double sum1 = 0;
            double sum2 = 0;
            double sum3 = 0;
            double sum4 = 0;

            for (int k = 0; k <= start; k++)
            {
                double this1 = (double)(MemoryEfficientModulus(16, (start - k), (8 * k + 1))) / (double)(8 * k + 1);
                double this2 = (double)(MemoryEfficientModulus(16, (start - k), (8 * k + 4))) / (double)(8 * k + 4);
                double this3 = (double)(MemoryEfficientModulus(16, (start - k), (8 * k + 5))) / (double)(8 * k + 5);
                double this4 = (double)(MemoryEfficientModulus(16, (start - k), (8 * k + 6))) / (double)(8 * k + 6);
                sum1 += this1;
                sum2 += this2;
                sum3 += this3;
                sum4 += this4;
            }
            for (int k = start + 1; k < INFINITY; k++)
            {
                double this1 = (Math.Pow(16, start - k) / (8 * k + 1));
                double this2 = (Math.Pow(16, start - k) / (8 * k + 4));
                double this3 = (Math.Pow(16, start - k) / (8 * k + 5));
                double this4 = (Math.Pow(16, start - k) / (8 * k + 6));
                sum1 += this1;
                sum2 += this2;
                sum3 += this3;
                sum4 += this4;
            }
            double total = 4 * sum1 - 2 * sum2 - sum3 - sum4;
            double floor = Math.Floor(total);
            total -= floor;
            int[] output = new int[number];
            for (int i = 0; i < number; i++)
            {
                total = total * 16;

                int think = (int)Math.Floor(total);
                if (think < 0)
                    think += 15;
                total -= think;
                output[i] = think;
            }
            return output;
        }
        public int MemoryEfficientModulus(int b, int e, int m)
        {
            int c = 1;
            int eprime = 0;
            while (eprime < e)
            {
                eprime++;
                c = (b * c) % m;
            }
            return c;

        }
    }
    public class Computer
    {
        public Socket Connection;
        public Computer(Socket Connection)
        {
            this.Connection = Connection;
        }
        public void Send(int data)
        {
            int NetworkInt = IPAddress.HostToNetworkOrder(data);
            byte[] buffer = BitConverter.GetBytes(NetworkInt);
            Connection.Send(buffer);
        }
        public void Send(string data)
        {
            byte[] buffer = ASCIIEncoding.ASCII.GetBytes(data);
            Connection.Send(buffer);
        }
        public void Send(long data)
        {
            long NetworkLong = IPAddress.HostToNetworkOrder(data);
            byte[] buffer = BitConverter.GetBytes(NetworkLong);
            Connection.Send(buffer);
        }
        public void Send(double data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            Connection.Send(buffer);
        }
        public void Send(bool data)
        {
            byte[] buffer = BitConverter.GetBytes(data);
            Connection.Send(buffer);
        }
        public int RecieveInt()
        {
            byte[] buffer = new byte[4];
            Connection.Receive(buffer);
            int NetworkInt = BitConverter.ToInt32(buffer, 0);
            int outInt = IPAddress.NetworkToHostOrder(NetworkInt);
            return outInt;
        }
        public long RecieveLong()
        {
            byte[] buffer = new byte[8];
            Connection.Receive(buffer);
            long NetworkLong = BitConverter.ToInt64(buffer, 0);
            long outInt = IPAddress.NetworkToHostOrder(NetworkLong);
            return outInt;
        }
        public double RecieveDouble()
        {
            byte[] buffer = new byte[sizeof(double)];
            Connection.Receive(buffer);
            double outDouble = BitConverter.ToDouble(buffer, 0);
            return outDouble;
        }
        public bool RecieveBool()
        {
            byte[] buffer = new byte[sizeof(bool)];
            Connection.Receive(buffer);
            bool outst = BitConverter.ToBoolean(buffer, 0);
            return outst;
        }
        public string RecieveString(int length)
        {
            byte[] buffer = new byte[length];
            Connection.Receive(buffer);
            string output = ASCIIEncoding.ASCII.GetString(buffer);
            return output;
        }
        public void Send(MathString mathString)
        {
            Send(mathString.negitive);
            Send(mathString.Integer.Length);
            Send(mathString.Integer);
            Send(mathString.Decimal.Length);
            Send(mathString.Decimal);
        }
        public MathString RecieveMathString()
        {
            bool negitive = RecieveBool();
            int IntegerLength = RecieveInt();
            string Integer = RecieveString(IntegerLength);
            int DecimalLength = RecieveInt();
            string Decimal = RecieveString(DecimalLength);
            MathString Output = new MathString(Integer, Decimal, negitive);
            return Output;
        }

    }
}
